/********************************************************************************************************************************
 * 《深入Linux设备驱动程序内核机制》-- 陈学松 著，电子工业出版社， 2012年1月第1次印刷
 * Date: 2024-04-19 19:16
 * Author: dimon.chen 
 * EMali: 1181302388@qq.com 
 * 
 * 章节: 第二章 内核内存管理
 *       
 * 
 ********************************************************************************************************************************/



// Linnux 内核物理内存管理机制，SLAB 机制，SLAB是基于物理内存页伙伴系统Buddy的机制，
// SLAB 先通过alloc_pages() 从物理内存 buddy 伙伴管理系统申请整块的物理页page frame，
// 然后把物理页 page 切分成大小相等的小的内存块，当申请内核申请的时候，从Slab管理的这些
// 小的内存块里申请到这些小的内存块，当内核释放掉这些内存块的时这些小内存不是直接返还给
// 伙伴系统，而是返回给Slab管理器。这样下一次申请这小的物理内存的时候就可以快速的申请到。
//
// slab 管理系统，智能申请到 ZONE_NORMAL 和 ZONE_DMA 低端内存区的物理地址，不能使用 
// ZONE_HIGH 高端内存区的物理地址。为什么有这样的限制？因为内核从slab申请的内存块地址
// 需要做线性映射到物理地址，CPU所能访问的地址都是虚拟地址，需要交给MMU转换为物理地址，
// 内核虚拟地址空间在低端 (0~896MB)+0xc000 0000 地址区，和物理地址是线性映射的有一个
// 固定的偏移量0xc000 0000 (内核用宏 PAGE_OFFSET 表示)，Slab使用低端内存去获取的物理地址块
// 的虚拟地址和物理内存之间就可以很容易的转换映射，降低了通过页目录表（页表）从内核虚拟地址
// 到物理地址空间映射的开销。
// 
// 内核中有很多频繁申请和释放的小的内存对象，向 task_struct, file_struct, 等，如果每次
// 都从物理内存伙伴管理系统中申请一个整个物理页，物理内存管理伙伴系统分配的基本但是页，
// 一个系统中典型的物理页 page 是 4KB，对于只需要一个小的内存对象申请一个整个页会造成
// 内存的浪费。所以在物理内存伙伴管理系统的基础上使用 SLAB 小内存管理机制，开对内核中
// 频繁申请和释放的小内存对象做管理和分配。
//


// SLAB 管理系统的内核框架接口函数有:
//   kmem_cache_create()
//   kmem_cahce_alloc()
//   kmem_cahce_free()
//   kmem_cache_destroy()
//
//
//



//
// 结构体: strcut kmem_cache;
//   头文件: <include/linux/slab_def.h>
//   struct kmem_cache {
//      struct array_cache *array[NR_CPUS];
//
//      const char *name;
//      struct list_head next;
//      
//   }



//
//  函数原型: kmem_cache_create();
//  头文件: <inlude/linux/slab.h>
//   
//      struct kmem_cache *kmem_cache_create(const char *,
//                              size_t size,
//                              size_t align,           //在内核中flag一般为0，使用默认值
//                              unsigned long flags,    //SLAB_HWCACHE_ALIGN
//                              void(*)(void*));
//
//

// 每一个内核模块都应该包含3个头文件


#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/slab.h>


struct my_struct{
    int count;
    int vid;
};


struct kmem_cache *mycachep = NULL;
struct my_struct *p[100];

int my_kmem_cache_init(void)
{
    int i = 0;
    

    printk("kmem_cache init\n");

    mycachep = kmem_cache_create("dimoncache", 
                                    sizeof(struct my_struct),
                                    0,
                                    SLAB_HWCACHE_ALIGN,
                                    NULL);
    if(!mycachep){
        printk("Slab create struct kmem_cache fail\n");
        return -ENOMEM;
    }

    printk("Slab create struct kmem_cache success\n");



    for(i=0; i< 100; i++){
        p[i] = kmem_cache_alloc(mycachep, GFP_KERNEL);
        if(!p[i]){
            printk("kmem_cache_alloc() fail\n");
        }
        else{
            printk("kmem_cache_alloc() p[%d]=%p __pa()=0x%08x\n", i, p[i], __pa(p[i]));
            if(i<26)
                memset(p[i], 'A' + i, sizeof(struct my_struct));
            else
             memset(p[i], 'X', sizeof(struct my_struct));
        }
    }



    return 0;
}

void my_kmem_cache_exit(void)
{
    int i = 0;
    int j = 0;

    printk("kmem_cache exit\n");

    printk("kmem_cache_free(cachep, objp);\n");
    for(i=0; i< 100; i++){
        if(p[i]){
            for(j=0; j<sizeof(struct my_struct); j++)
            {
                printk("%02x ", ((unsigned char*)p[i])[j]);
            }
            printk("\n");

            kmem_cache_free(mycachep, p[i]);
        }
    }

    printk("kmem_cache_destroy(cachep);\n");
    kmem_cache_destroy(mycachep);    
}

module_init(my_kmem_cache_init);
module_exit(my_kmem_cache_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("dimon.chen 1181302388@qq.com");
MODULE_DESCRIPTION("test module for SLAB kmem_cache_create()");

// 哈哈，遇到了错误，应该是 MODULE_LICENSE(), MODULE_AUTHOR(), MODULE_DESCRIPTION(), 不是 GPL_XXX打头
// GPL_LISCENSE("GPL");
// GPL_AUTHOR("dimon.chen 1181302388@qq.com");
// GPL_DESCRIPTION("test module for SLAB kmem_cache_create()");